package com.example.test_2_1_transaction;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Test21TransactionApplication {

    public static void main(String[] args) {
        SpringApplication.run(Test21TransactionApplication.class, args);
        // **** 事务 ****
        // 大体的认识在 mysql 阶段讲解了一部分了

        // 事务的操作主要有三步:
        //1. 开启事务: start transaction/ begin (⼀组操作前开启事务)
        //2. 提交事务: commit (这组操作全部成功, 提交事务)
        //3. 回滚事务: rollback (这组操作中间任何⼀个操作出现异常, 回滚事务)

        // **** Spring 中 事务的实现 *****
        // Spring 中的事务 分为两种:
        // 1. 编程式事务(⼿动写代码操作事务). (手动的实现 开启, 提交, 回滚 事务)
        // 2. 声明式事务(利⽤注解⾃动开启和提交事务).

        // 先看看 编程式方式 , 这种方式以了解就好
        // SpringBoot 内置了两个对象:
        //1. DataSourceTransactionManager 事务管理器. ⽤来获取事务(开启事务), 提交或回滚事务的
        //2. TransactionDefinition 是事务的属性, 在获取事务的时候需要将
        //TransactionDefinition 传递进去从⽽获得⼀个事务 TransactionStatus

        // 直接把这个注入进来就可以了, 在 controller 层里面

        // 接下来看看声明式事务
        // 还是 在 controller 里面执行 TranController 类

        // 实验时候发现
        // Transactional 只有 遇到 运行时 异常 才会进行回滚
        //          所以 Transactional 只有 对 RunTimeException 类型的异常 以及其子类, 和 Error 异常的子类 有效
        //          其他类型的异常没有效果

        // 同时 Transactional 可以让我们对其默认的属性进行修改, 让其不止这几个类型才会有效
        //              有遇到那些异常进行回滚, 和 遇到哪些异常不会回滚的 机制

        // 最重要的是 : Propagation propagation() default Propagation.REQUIRED; // 事务传播机制
        //
        //    Isolation isolation() default Isolation.DEFAULT;  // 隔离级别 (数据库的隔离级别)

        // ****  @Transactional 详解 ****
        //  @Transactional 注解当中的三个常⻅属性:
        //1. rollbackFor: 异常回滚属性. 指定能够触发事务回滚的异常类型. 可以指定多个异常类型
        //2. Isolation: 事务的隔离级别. 默认值为 Isolation.DEFAULT
        //3. propagation: 事务的传播机制. 默认值为 Propagation.REQUIRED

        // **** 事务隔离级别 ****
        // sql 标准定义了隔离界别
        //1.读未提交(READ UNCOMMITTED): 读未提交, 也叫未提交读. 该隔离级别的事务可以看到其他事务中未提交的数据.
        // 因为其他事务未提交的数据可能会发⽣回滚, 但是该隔离级别却可以读到,
        // 我们把该级别读到的数据称之为脏数据, 这个问题称之为脏读.
        //2. 读提交(READ COMMITTED): 读已提交, 也叫提交读. 该隔离级别的事务能读取到已经提交事务的数据,
        //该隔离级别不会有脏读的问题.但由于在事务的执⾏中可以读取到其他事务提交的结果,
        //所以在不同时间的 相同 SQL 查询 可能会得到不同的结果, 这种现象叫做不可重复读
        //3. 可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交.
        // 也就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的.
        // 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别.
        // ⽐如此级别的事务正在执⾏时, 另⼀个事务成功的插⼊了某条数据,
        // 但因为它每次查询的结果都是⼀样的, 所以会导致查询不到这条数据,
        // ⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因). (对事务的删除是可以读到的)
        // 明明在事务中查询不到这条信息，但⾃⼰就是插⼊不进去, 这个现象叫幻读.
        //4. 串⾏化(SERIALIZABLE): 序列化, 事务最⾼隔离级别. 它会强制事务排序, 使之不会发⽣冲突,
        // 从⽽解决了脏读, 不可重复读和幻读问题, 但因为执⾏效率低, 所以真正使⽤的场景并不多.


        // ***** Spring 事务的隔离级别 *****
        // Spring 中事务隔离级别有5 种:
        //1. Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主. (多出来的)
        //      (你的事务隔离级别是什么,我的隔离级别就是什么, 这个是可以进行修改的)
        //2. Isolation.READ_UNCOMMITTED : 读未提交, 对应SQL标准中 READ UNCOMMITTED
        //3. Isolation.READ_COMMITTED : 读已提交,对应SQL标准中 READ COMMITTED
        //4. Isolation.REPEATABLE_READ : 可重复读, 对应SQL标准中 REPEATABLE READ
        //5. Isolation.SERIALIZABLE : 串⾏化, 对应SQL标准中 SERIALIZABLE
        // Spring 支持很多中类型的数据库, 不仅仅是 MySql

        // Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置

        // **** Spring事务的传播机制 ****
        // 事务传播机制就是: 多个事务⽅法存在调⽤关系时, 事务是如何在这些⽅法间进⾏传播的
        // Transactional 可以修饰方法, 也可以修饰类, 修饰类的话就表示 对这个类下面的所有方法都加上了这个注解

        // ⽐如有两个⽅法A, B都被 @Transactional 修饰, A⽅法调⽤B⽅法
        // A⽅法运⾏时, 会开启⼀个事务. 当A调⽤B时, B⽅法本⾝也有事务, 此时B⽅法运⾏时, 是加⼊A的事务, 还
        //是创建⼀个新的事务呢?

        // Propagation propagation() default Propagation.REQUIRED; // 事务传播机制
        // 具体怎么传播, 这就与他的配置有关了

        // 事务传播机制都有
        // @Transactional 注解⽀持事务传播机制的设置, 通过 propagation 属性来指定传播⾏为.
        //Spring 事务传播机制有以下 7 种：
        //1. Propagation.REQUIRED : 默认的事务传播级别. 如果当前存在事务, 则加⼊该事务. 如果当前没
        //有事务, 则创建⼀个新的事务. (这几个方法会公用一个事务)
        //2. Propagation.SUPPORTS : 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则以⾮事务的
        //⽅式继续运⾏. (所以 并不是 加了Transactional , 事务就一定会生效的)
        //3. Propagation.MANDATORY :强制性. 如果当前存在事务, 则加⼊该事务. 如果当前没有事务, 则
        //抛出异常. (要求必须有事务, 没有事务不行)
        //4. Propagation.REQUIRES_NEW : 创建⼀个新的事务. 如果当前存在事务, 则把当前事务挂起. 也
        //就是说不管外部⽅法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部⽅法都会新开
        //启⾃⼰的事务, 且开启的事务相互独⽴, 互不⼲扰. (不同方法独自进行自己的事务,相互独立)
        //5. Propagation.NOT_SUPPORTED : 以⾮事务⽅式运⾏, 如果当前存在事务, 则把当前事务挂起(不⽤).
        //                 (有了事务也不用, 就是以没有事务的方式运行, 这个操作是防止其他方法也加了事务,进行了关联)
        //6. Propagation.NEVER : 以⾮事务⽅式运⾏, 如果当前存在事务, 则抛出异常.
        //                  (这个方法就不能在事务里面运行,其他关联的类或者方法中也不能存在事务)
        // 7. Propagation.NESTED : 如果当前存在事务, 则创建⼀个事务作为当前事务的嵌套事务来运⾏.
        //如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED . (嵌套 , 可以理解为 子事务, 他们之间是有一定关系的)


        // 接下来通过代码演示
        // 对于以上事务传播机制，重点关注以下两个：
        //1. REQUIRED（默认值）
        //2. REQUIRES_NEW
        // 这时候用两张表来演示了

        // 那么 REQUIRED 和 NESTED 有什么区别呢?
        // NESTED和REQUIRED区别
        //• 整个事务如果全部执⾏成功, ⼆者的结果是⼀样的.
        //• 如果事务⼀部分执⾏成功, REQUIRED加⼊事务会导致整个事务全部回滚. NESTED嵌套事务可以实
        //现局部回滚, 不会影响上⼀个⽅法中执⾏的结果.
        //嵌套事务之所以能够实现部分事务的回滚, 是因为事务中有⼀个保存点(savepoint)的概念, 嵌套事务
        //进⼊之后相当于新建了⼀个保存点, ⽽滚回时只回滚到当前保存点.

        // 以上问题都在 controller 层的 ProController 演示 还有区别也在
    }

}
